package com.qdd.freight.handler;

import com.qdd.freight.exception.FreightCountFailureException;
import com.qdd.freight.exception.NullAvailableParamsException;
import com.qdd.freight.mapper.LogisticsRegionMapper;
import com.qdd.freight.pojo.Logistics;
import com.qdd.freight.pojo.LogisticsRegion;
import com.qdd.freight.pojo.Ship;
import org.springframework.beans.factory.annotation.Autowired;
import tk.mybatis.mapper.entity.Example;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

/**
 * @Author Yan
 * @Description //TODO
 * @Date 13:02 2019/12/30
 **/

abstract public class AbstractLogisticsHandler extends AbstractHandler {

    @Autowired
    private LogisticsRegionMapper logisticsRegionMapper;

    /**
     * 子类可继承方法
     * 判断比较数所在的级别
     *
     * @param comparand 比较数：采购数量/体积/重量
     * @param levelRule 级别字符换
     * @return 返回所在数组的索引值
     */
    protected Integer getLevelByComparand(Double comparand, String levelRule) {
        //解析级别规则字符串
        String[] levelArr = levelRule.split(",");
        //定义区间集合
        List<int[]> range = new ArrayList<>();
        //把数组转换为范围区间集合
        //集合中的每个数组中存有三个元素：当前范围下的起始值、结束值、levelArr数组中的索引值
        for (int i = 0; i <= levelArr.length; i++) {
            if (i == 0) {
                range.add(new int[]{1, Integer.parseInt(levelArr[0]), i});
            } else if (i <= levelArr.length - 1) {
                //如果在中间则使用区间[begin ,end）
                range.add(new int[]{Integer.parseInt(levelArr[i - 1]), Integer.parseInt(levelArr[i]), i});
            } else {
                range.add(new int[]{Integer.parseInt(levelArr[i - 1]), Integer.MAX_VALUE, i});
            }
        }
        //判断区间,返回所在的索引
        Integer index = range.stream()
                .filter(item -> comparand > item[0] && comparand <= item[1])
                .map(item -> item[2])
                .findFirst().get();
        return index;
    }

    /**
     * 内部方法
     * 获取最终运价
     *
     * @param index         在数组中的索引位置
     * @param defaultPrice  默认价格
     * @param startingPrice 起步价格
     * @param levelRule     阶梯字符串
     * @param levelPrice    价格阶梯字符串
     * @return
     */
    protected Double getFreightByLevel(Integer index, Double defaultPrice, Double startingPrice, String levelRule, String levelPrice) {
        //解析级别规则字符串
        String[] levelArr = levelRule.split(",");
        //如果小于最低级别，按照起步价格
        if (index == 0) {
            System.out.println("级别等级为：" + printArr(levelArr));
            System.out.println("级别价格为：" + printArr(levelPrice.split(",")));
            System.out.println("当前所处档位级别为第 " + (index + 1) + " 档。");
            return startingPrice;
        }
        //判断是否超出最高级别。如果超出返回默认价格
        if (index == levelArr.length) {
            System.out.println("级别等级为：" + printArr(levelArr));
            System.out.println("级别价格为：" + printArr(levelPrice.split(",")));
            System.out.println("当前所处档位级别为第 " + (index + 1) + " 档。");
            return defaultPrice;
        }
        //在区间内，判断出具体的等级对应的价格
        System.out.println("级别等级为：" + printArr(levelArr));
        System.out.println("级别价格为：" + printArr(levelPrice.split(",")));
        System.out.println("当前所处档位级别为第 " + (index + 1) + " 档。");
        //解析级别价格字符串,获得价格
        return Double.parseDouble(levelPrice.split(",")[index]);
    }

    private String printArr(String[] arr) {
        StringBuilder builder = new StringBuilder(arr.length);
        builder.append("[");
        for (String s : arr) {
            builder.append(s + ",");
        }
        builder.append("]");
        return builder.toString();
    }

    /**
     * 内部方法
     * 按体积和重量计算----判断是否设置了整箱体积：
     * 1.如果设置了整箱体积/重量（详见商品信息设置部分），则判断能否根据采购数量+箱规转换率转换为整箱：
     * 1.1如果能，则按照整箱的体积+余数的体积进行计算；
     * 1.2.如果不能，则直接按照单件的体积*购买数量进行阶梯判断和计算；
     * 2.如果没有设置整箱体积/重量，按照单件的体积*购买数量进行阶梯判断和计算；
     * 3.超过最大档位按照默认费率。
     *
     * @param quantity
     * @param boxVolumeOrWeight
     * @param singleVolumeOrWeight
     * @param QTYRate
     * @return
     */
    protected Double getTotalWeightOrVolume(Integer quantity, Double boxVolumeOrWeight, Double singleVolumeOrWeight, Integer QTYRate) {
        //1.如果没有设置整箱体积/重量，按照单件的体积*购买数量进行阶梯判断和计算；
        if (boxVolumeOrWeight == null || boxVolumeOrWeight == 0) {
            return singleVolumeOrWeight * quantity;
        }
        /*
            2.判断能否根据采购数量+箱规转换率转换出整箱：
         */
        //2.1.如果不能，则直接按照单件的体积*购买数量进行阶梯判断和计算；
        if (quantity < QTYRate) {
            return singleVolumeOrWeight * quantity;
        }
        //2.2如果采购数量大于箱规，则能转换出整箱，则按照整箱的体积+余数的体积进行计算；
        return (quantity / QTYRate) * boxVolumeOrWeight + (quantity % QTYRate) * singleVolumeOrWeight;
    }

    /**
     * 获得最终用来计算的计价体系参数
     * @param logistics 物流承运商对象
     * @param ifLevel 是否存在级别设置标志位,0-不存在,1-存在
     * @param logisticsId 物流承运商Id
     * @param consignee 仓库及收货地址字符串,结构为:收货省市区-到货省市区:"XX_YY_ZZ-AA_BB_CC"
     * @return
     * @throws FreightCountFailureException
     */

    /**
     * 获取可用的计算参数
     *
     * @param ship      入参其实现类,快递或物流对象
     * @param shipId    快递/物流id
     * @param consignee
     * @return
     * @throws NullAvailableParamsException
     */
    protected Map<String, Object> getAvailableParams(Ship ship, Integer shipId, String consignee) throws NullAvailableParamsException {
        Logistics logistics = (Logistics) ship;
        //1.获取物流对象对象
        Map<String, Object> paramMap = new HashMap<>();
        //2.获取计价方式
        Double defaultPrice = logistics.getDefaultPrice();
        Double startingPrice = logistics.getStartingPrice();
        String levelRule = logistics.getLevelRule();
        String levelPrice = logistics.getLevelPrice();

        //3.以关联的物流Id和到货地区为条件，获取该收获地址下是否存在特殊价格设置
        String[] receiverArr = consignee.split("-");//到货地区
        String receiverAddr = receiverArr[1];//当前收件地址
        //获取该物流对象下所有关联的特殊计价地区清单
        Example example = new Example(LogisticsRegion.class);
        example.createCriteria().andEqualTo("logisticsId", shipId);
        LogisticsRegion logisticsRegion = logisticsRegionMapper.selectOneByExample(example);
        //4.如果存在特殊价格设置则以特殊价格进行运费计算
        //4.1判断特殊计价清单中是否包含当前收货地址(注意省市区的层级所属关系,判断包含而不是等于)
        boolean isInclude = false;//默认没有禁运设置
        //判断当前收件地址是否以禁运清单中的地址开头
        if (logisticsRegion != null) {
            String regionArr = logisticsRegion.getConsignee();
            isInclude = Stream.of(regionArr)
                    .anyMatch(receiverAddr::startsWith);
        }
        //4.2如果存在,使用特殊计价覆盖
        if (isInclude) {
            defaultPrice = logisticsRegion.getDefaultPrice();
            startingPrice = logisticsRegion.getStartingPrice();
            levelRule = logisticsRegion.getLevelRule();
            levelPrice = logisticsRegion.getLevelPrice();
        }
        //5.对计价参数进行数据校验
        if (defaultPrice == null || startingPrice == null) {
            throw new NullAvailableParamsException("未获取到正确的计数阶梯数据。");
        }
        if (levelRule == null || levelRule.length() == 0) {
            throw new NullAvailableParamsException("未获取到正确的计数阶梯数据。");
        }
        if (levelPrice == null || levelPrice.length() == 0) {
            throw new NullAvailableParamsException("未获取到正确的计价参数。");
        }
        //6.封装可用参数
        paramMap.put("defaultPrice", defaultPrice);
        paramMap.put("startingPrice", startingPrice);
        paramMap.put("levelRule", levelRule);
        paramMap.put("levelPrice", levelPrice);
        return paramMap;
    }
}
